#include<iostream>
#include<cstdio>
using namespace std;
#include<cstdlib>
#include<unistd.h>
#include<cstring>
#include<string>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>

const int basesize = 1024; 
const int argvnum = 64;
const int envnum = 64;

//全局的命令行参数表
char *gargv[argvnum];
int gargc = 0;

//我的系统的环境变量表
char *genv[envnum];

//全局变量,程序的执行结果（退出码）
int lastcode = 0;


//全局的shell的当前工作路径
char pwd[basesize];

char pwdenv[basesize];

//全局变量与重定向有关
#define NoneRedir   0
#define InputRedir  1
#define OutputRedir 2
#define AppendRedir 3

int redir = NoneRedir;
char* filename = nullptr;

//"   "file.txt
#define TrimSpace(pos) do{\
	while(isspace(*pos)){\
	    pos++;\
	    }\
}while(0)
//-----------------------------------------------------------------------------------------------
string GetUserName()
{
    string name = getenv("USER");

    return name.empty() ? "None" : name;
}

string GetHostName()
{
    string hostname = getenv("HOSTNAME");

    return hostname.empty() ? "None" : hostname;
}

string GetPwd()
{
    if(nullptr == getcwd(pwd,sizeof(pwd)))
	return "None";

    snprintf(pwdenv, sizeof(pwdenv), "PWD=%s",pwd);
    putenv(pwdenv); //PWD=AAA;
    return pwd;

    //string pwd = getenv("PWD");
    //return pwd.empty() ? "None" : pwd;
}

string LastDir()
{
    string cur = GetPwd();
    if(GetPwd() == "/" || GetPwd() == "None")
    {
	return GetPwd();
    }
    //  /home/yjl/lesson18
    size_t pos = cur.rfind("/");
    if(pos == string::npos)
	return cur;
    return cur.substr(pos + 1);
}

string MakeCommandLine()
{
    //[yjl@hcss-ecs-0865 myshell]$
    char command_line[basesize];
    snprintf(command_line, basesize, "[%s@%s %s]# ",
             GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());//将这三个参数按照指定的格式拼接到command_line中
	return command_line;
}


void PrintCommandLine()  //1.命令行提示符
{
    printf("%s",MakeCommandLine().c_str());
    fflush(stdout);  //刷新缓冲区，确保输出内容立即显示
}

//--------------------------------------------------------------------------------------------

bool GetCommandLine(char command_buffer[], int size)    //2.获取用户命令
{
    //我们将用户输入的命令行，当做完整的一个字符串 "ls -a -l"
    char* result = fgets(command_buffer, size, stdin);
    if(!result)
    {
	return false;
    }

    command_buffer[strlen(command_buffer) - 1] = 0; //将最后一个位置'\n'覆盖掉	
    if(strlen(command_buffer) == 0)
	return false;
    return true;
}

//----------------------------------------------------------------------------------------------
void ReSetCommandLine()  //初始化命令行
{
    memset(gargv, 0, sizeof(gargv)); //每次使用都将gargv初始化成0(清空)
    gargc = 0;

    //重定向
    redir = NoneRedir;
    filename = nullptr;
}

void ParseRedir(char command_buffer[], int len) //解析重定向
{
    //"ls -a -l"
    //"ls -a -l">file.txt
    //"ls -a -l">>file.txt
    //"ls -a -l"<file.txt
    int end = len - 1;
    //printf("command start:%s\n",command_buffer);
    while(end >= 0 )
    {
	if(command_buffer[end] == '<')
	{
	    redir = InputRedir;
	    filename = &command_buffer[end+1];
	    TrimSpace(filename);
	    command_buffer[end] = 0;
	    break;
	}
	else if(command_buffer[end] == '>')
	{
	    if(command_buffer[end-1] == '>')
	    {
		redir = AppendRedir;
		command_buffer[end] = 0;
		command_buffer[end-1] = 0;
		filename = &command_buffer[end+1];
		TrimSpace(filename);
		break;
	    }
	    else
	    {
		redir = OutputRedir;
		command_buffer[end] = 0;
		filename = &command_buffer[end+1];
		TrimSpace(filename);
		break;
	    }
	}
	else
	    end--;
    }
}

void ParseCommand(char command_buffer[])
{
    //"ls -a -l"
    //进行切割，strtok底层是：碰到 " " 就将其变成 '\0'
    const char* sep = " ";
    gargv[gargc++] = strtok(command_buffer, sep);
    //strtok函数有特点，除了第一次传command_buffer,后面要传nullptr

    //切割完毕时会返回nullptr
    while(gargv[gargc++] = strtok(nullptr, sep)); //表示切的是历史下一个字符串
    gargc -= 1;
}
void ParseCommandLine(char command_buffer[],int len)  //3.分析命令
{
    ReSetCommandLine();
    ParseRedir(command_buffer,len);
    ParseCommand(command_buffer);
}

void debug()
{
    printf("gargc:%d\n",gargc);

    int i = 0;
    for(i = 0; gargv[i]; i++)
    {
	printf("gargv[%d]:%s\n",i,gargv[i]);
    }
}

//-----------------------------------------------------------------------------------------------
//在shell中
//有些命令需要子进程执行，
//而有些命令需要shell自己执行---叫做内建命令(built command)

void DoRedir()
{
        //重定向由子进程完成
	//程序替换会不会影响重定向？不会
	if(redir == InputRedir)
	{
	    if(filename)
	    {
		int fd = open(filename, O_RDONLY);
		if(fd < 0)
		{
		    exit(2);
		}
		dup2(fd,0);
	    }
	    else
	    {
		exit(1);
	    }
	}
	else if(redir == OutputRedir)
	{
	    if(filename)
	    {
		int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
		if(fd < 0)
		{
		    exit(4);
		}
		dup2(fd,1);
	    }
	    else
	    {
		exit(3);
	    }
	}
	else if(redir == AppendRedir)
	{
	    if(filename)
	    {
		int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
		if(fd < 0)
		{
		    exit(6);
		}
		dup2(fd,2);
	    }
	    else
	    {
		exit(5);
	    }
	}
	else
	{
	    //没有重定向，DoNothing
	}
}
bool ExecuteCommand()    //4.执行命令
{
    pid_t id = fork();

    if(id < 0) return false;
    if(id == 0)
    {
	//child
	// 0.先判断&&重定向
        DoRedir();
	// 1.执行命令(exec*)
	execvpe(gargv[0], gargv, genv);
	// 2.退出(exit());
	exit(1);  //只有子进程程序替换失败时才exit()
	
    }
    //parent
    int status = 0;
    
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
	if(WIFEXITED(status))
	{
	    lastcode = WEXITSTATUS(status);
	}
	else
	{
	    lastcode = 100;
	}
	return true;
    }
    return false;
}

void AddEnv(const char *item)
{
    int index = 0;
    while(genv[index])
    {
	index++;
    }

    //找到了genv的最后一个元素
    genv[index] = (char*)malloc(strlen(item)+1);
    strncpy(genv[index], item, strlen(item)+1);
    index++;
    genv[index] = nullptr;
}
//shell自己执行命令，本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{
    //if(gargv[0] == "cd") // 错误，比较的是地址
    if(strcmp(gargv[0],"cd") == 0) //正确，比较的是内容
    {
	//内建命令
	if(gargc == 2)
	{
	    chdir(gargv[1]);
	    lastcode = 0;
	   
	}
	else
	{
	    lastcode = 1;
	}
	return true;
    }
    else if(strcmp(gargv[0],"export") == 0)
    {
	    //export也是内建命令
	    if(gargc == 2)
	{
	    AddEnv(gargv[1]); //不能用putenv,因为putenv是我自己维护的
	    lastcode = 0;
	}
	else
	    lastcode = 2;

	    return true;
    }
    else if(strcmp(gargv[0],"env") == 0)
    {
	    for(int i = 0; genv[i]; i++ )
	    {
		printf("%s\n",genv[i]);
	    }
	    lastcode = 0;
	    return true;
    }
    else if(strcmp(gargv[0],"echo") == 0)
    {
	if(gargc == 2)
	{
	    //echo $?
	    //echo $PATH
	    //echo hello
	    if(gargv[1][0] == '$')
	    {
		if(gargv[1][1] == '?')
		{
		    printf("%d\n", lastcode);
		    lastcode = 0;
		}
	    }
	    else
	    {
		printf("%s\n",gargv[1]);
		lastcode = 0;

	    }

	}
	else
	{
	    lastcode = 3;
	}
	return true;
    }

    return false;
}

//作为shell，应该从系统的配置文件中获取环境变量
//myshell无法实现，只能从系统shell中获取
void InitEnv()
{
    extern char **environ;
    int index = 0;
    while(environ[index])
    {
	genv[index] = (char*)malloc(strlen(environ[index] +1));
	strncpy(genv[index], environ[index], strlen(environ[index]+1));
	index++;
    }
}
int main()
{
    InitEnv();
    char command_buffer[basesize];

    while(true)
    {

        PrintCommandLine();  //1.命令行提示符
	//command_buffer -> output : 输出型参数
        if(!GetCommandLine(command_buffer, basesize))    //2.获取用户命令
	{
	    continue;
	}

	//printf("%s",command_buffer);
	
	//"ls -a -l" -> "ls" "-a" "-l"
	//"ls -a -l">"hello.txt"
	//"ls -a -l">>"hello.txt"
	//"ls -a -l"<"hello.txt"
        ParseCommandLine(command_buffer, sizeof(command_buffer));  //3.分析命令
	//debug();
	//需要检测是否是内建命令并执行
	if(CheckAndExecBuiltCommand())
	{
	    continue;
	}
	
        ExecuteCommand();    //4.执行命令
    }


   

    return 0;
}	

